package misc

import (
	"gvm/native/basic"
	"gvm/rtda"
	"gvm/rtda/heap"
)

func RegisterUnsafe() (className string, methods []*basic.NaMethod) {
	return "sun/misc/Unsafe", []*basic.NaMethod{
		{
			"arrayBaseOffset",
			"(Ljava/lang/Class;)I",
			arrayBaseOffset,
		}, {
			"arrayIndexScale",
			"(Ljava/lang/Class;)I",
			arrayIndexScale,
		}, {
			"addressSize",
			"()I",
			addressSize,
		}, {
			"objectFieldOffset",
			"(Ljava/lang/reflect/Field;)J",
			objectFieldOffset,
		}, {
			"compareAndSwapObject",
			"(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z",
			compareAndSwapObject,
		}, {
			"getIntVolatile",
			"(Ljava/lang/Object;J)I",
			getInt,
		}, {
			"compareAndSwapInt",
			"(Ljava/lang/Object;JII)Z",
			compareAndSwapInt,
		}, {
			"getObjectVolatile",
			"(Ljava/lang/Object;J)Ljava/lang/Object;",
			getObject,
		}, {
			"compareAndSwapLong",
			"(Ljava/lang/Object;JJJ)Z",
			compareAndSwapLong,
		},
	}

}

// public native int arrayBaseOffset(Class<?> type);
// (Ljava/lang/Class;)I
func arrayBaseOffset(jFrame *rtda.JFrame) {
	stack := jFrame.OperandStack()
	stack.PushInt(0) // todo
}

// public native int arrayIndexScale(Class<?> type);
// (Ljava/lang/Class;)I
func arrayIndexScale(jFrame *rtda.JFrame) {
	stack := jFrame.OperandStack()
	stack.PushInt(1) // todo
}

// public native int addressSize();
// ()I
func addressSize(jFrame *rtda.JFrame) {
	// vars := jFrame.LocalVars()
	// vars.GetRef(0) // this

	stack := jFrame.OperandStack()
	stack.PushInt(8) // todo unsafe.Sizeof(int)
}

// public native long objectFieldOffset(Field field);
// (Ljava/lang/reflect/Field;)J
func objectFieldOffset(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	jField := vars.GetRef(1)

	offset := jField.GetIntVar("slot", "I")

	stack := jFrame.OperandStack()
	stack.PushLong(int64(offset))
}

// public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x)
// (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z
func compareAndSwapObject(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	obj := vars.GetRef(1)
	fields := obj.Data()
	offset := vars.GetLong(2)
	expected := vars.GetRef(4)
	newVal := vars.GetRef(5)

	// todo
	if anys, ok := fields.(heap.Slots); ok {
		// object
		swapped := _casObj(obj, anys, offset, expected, newVal)
		jFrame.OperandStack().PushBoolean(swapped)
	} else if objs, ok := fields.([]*heap.Object); ok {
		// ref[]
		swapped := _casArr(objs, offset, expected, newVal)
		jFrame.OperandStack().PushBoolean(swapped)
	} else {
		// todo
		panic("todo: compareAndSwapObject!")
	}
}
func _casObj(obj *heap.Object, fields heap.Slots, offset int64, expected, newVal *heap.Object) bool {
	current := fields.GetRef(uint(offset))
	if current == expected {
		fields.SetRef(uint(offset), newVal)
		return true
	} else {
		return false
	}
}
func _casArr(objs []*heap.Object, offset int64, expected, newVal *heap.Object) bool {
	current := objs[offset]
	if current == expected {
		objs[offset] = newVal
		return true
	} else {
		return false
	}
}

// public native boolean getInt(Object o, long offset);
// (Ljava/lang/Object;J)I
func getInt(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	fields := vars.GetRef(1).Data()
	offset := vars.GetLong(2)

	stack := jFrame.OperandStack()
	if slots, ok := fields.(heap.Slots); ok {
		// object
		stack.PushInt(slots.GetInt(uint(offset)))
	} else if shorts, ok := fields.([]int32); ok {
		// int[]
		stack.PushInt(int32(shorts[offset]))
	} else {
		panic("getInt!")
	}
}

// public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
// (Ljava/lang/Object;JII)Z
func compareAndSwapInt(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	fields := vars.GetRef(1).Data()
	offset := vars.GetLong(2)
	expected := vars.GetInt(4)
	newVal := vars.GetInt(5)

	if slots, ok := fields.(heap.Slots); ok {
		// object
		oldVal := slots.GetInt(uint(offset))
		if oldVal == expected {
			slots.SetInt(uint(offset), newVal)
			jFrame.OperandStack().PushBoolean(true)
		} else {
			jFrame.OperandStack().PushBoolean(false)
		}
	} else if ints, ok := fields.([]int32); ok {
		// int[]
		oldVal := ints[offset]
		if oldVal == expected {
			ints[offset] = newVal
			jFrame.OperandStack().PushBoolean(true)
		} else {
			jFrame.OperandStack().PushBoolean(false)
		}
	} else {
		// todo
		panic("todo: compareAndSwapInt!")
	}
}

// public native Object getObject(Object o, long offset);
// (Ljava/lang/Object;J)Ljava/lang/Object;
func getObject(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	fields := vars.GetRef(1).Data()
	offset := vars.GetLong(2)

	if anys, ok := fields.(heap.Slots); ok {
		// object
		x := anys.GetRef(uint(offset))
		jFrame.OperandStack().PushRef(x)
	} else if objs, ok := fields.([]*heap.Object); ok {
		// ref[]
		x := objs[offset]
		jFrame.OperandStack().PushRef(x)
	} else {
		panic("getObject!")
	}
}

// public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
// (Ljava/lang/Object;JJJ)Z
func compareAndSwapLong(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	fields := vars.GetRef(1).Data()
	offset := vars.GetLong(2)
	expected := vars.GetLong(4)
	newVal := vars.GetLong(6)

	if slots, ok := fields.(heap.Slots); ok {
		// object
		oldVal := slots.GetLong(uint(offset))
		if oldVal == expected {
			slots.SetLong(uint(offset), newVal)
			jFrame.OperandStack().PushBoolean(true)
		} else {
			jFrame.OperandStack().PushBoolean(false)
		}
	} else if longs, ok := fields.([]int64); ok {
		// long[]
		oldVal := longs[offset]
		if oldVal == expected {
			longs[offset] = newVal
			jFrame.OperandStack().PushBoolean(true)
		} else {
			jFrame.OperandStack().PushBoolean(false)
		}
	} else {
		// todo
		panic("todo: compareAndSwapLong!")
	}
}
